﻿using System;
using System.Collections.Generic;
using System.Text;

using Framework;

namespace SchemaTrading
{
    #region Enumerations
    //Set of possible Outlooks (for tomorrow)
    public enum EOutlook
    {
        Long,
        Short,
        Neutral,
        Stop
    }
    public enum ETrade
    {
        Buy,
        Sell,
        Cancel
    }
    public enum EDirection
    {
        Long,
        Short
    }
    public enum EReason
    {
        OvernightStop,
        DaytimeStop,
        YesterdaySaysLong,
        YesterdaySaysShort,
        YesterdaySaysStop,
        SeedCapitalIsGone,
        OpenBelowLongStop,
        OpenAboveShortStop
    }
    #endregion

    //Start with $1,000 to invest, invest 2% on Long/Short as opportunities come up
    public class CSimulation
    {
        #region Static Constants
        //Fixed Tuning Parameters
        public static double START = 1000;   //$100,000
        public static double LEVERAGE = 100;
        public static double INVEST = 0.02; //2% per opportunity, times leverage
        public static bool INVEST_MORE_IF_ADX_IS_HIGHER = false;
        public static bool KEEP_INVESTING_WHILE_BUY = false;
        public static bool STOP_WHEN_SEED_CAPITAL_GONE = false;
        public static bool CANCEL_OPEN_TRADES_IF_STOP = false;
        #endregion

        #region Members
        //Stored State information
        public double Held;     //Not invested
        public double LongQty;  //Long as a quantity
        public double LongCv;   //Long as current value
        public double ShortQty; //Short as a quantity
        public double ShortCv;  //Short as current value

        public EOutlook Outlook;    //Recommendation for tommorrow
        public CNoteList Notes = new CNoteList();
        private CData _current = null;
        #endregion

        #region Properties
        //Computed State information
        public double Total { get { return Held + LongCv + ShortCv; } }         //Net position
        public double Profit { get { return Total - START * LEVERAGE; } }
        public double PointValue { get { return null == _current ? double.NaN : (LongCv + ShortCv) * _current.Series.SeriesPointSize / _current.DataClose; } } 
        public double Exposure //% invested of total
        { 
            get 
            { 
                if (double.IsNaN(_exposure)) 
                    _exposure = (LongCv + ShortCv) / Total; 
                return _exposure; 
            } 
        }
        private double _exposure = double.NaN;
        public double StopLong  { get { return null == _current ? double.NaN : _current.StopLong; } }
        public double StopShort { get { return null == _current ? double.NaN : _current.StopShort; } }
        #endregion

        #region Constructors
        //One of these is executed for each new data point that arrives
        // - Case #1: Initial state
        private CSimulation()
        {
            //Outlook
            Outlook = EOutlook.Neutral;

            //Balances
            Held = START * LEVERAGE;
            LongQty = 0;
            ShortQty = 0;
        }
        // - Case #2: First point
        public CSimulation(CData current) : this(new CSimulation(), current) { }
        // - Case #3: Subsequent points
        public CSimulation(CSimulation prev, CData current)
        {
            _current = current;

            //1. Continuity - Transfer balances
            this.Held = prev.Held;
            this.LongQty = prev.LongQty;
            this.ShortQty = prev.ShortQty;

            //2. Overnight activity (happens before current point arrives)
            //2a. Manual - Decide how much to invest tomorrow
            double investable = this.Held * INVEST; //2% (20-30)
            if (INVEST_MORE_IF_ADX_IS_HIGHER)
            {
                if (current.ADX > 60) investable *= 3.0;  //6%    (60+)
                else if (current.ADX > 50) investable *= 2.5;  //5%    (50-60)
                else if (current.ADX > 40) investable *= 2.0;  //4%    (40-50)
                else if (current.ADX > 30) investable *= 1.5;  //3%    (30-40)
                else if (current.ADX < 20) investable *= 0.5;  //1%    (15-20)
            }
            if (investable < 0) investable = 0;
            if (investable > this.Held) investable = Held;
            
            //2b. Automatic - Overnight stops
            if (LongQty  > 0 && current.DataOpen < StopLong  && StopLong  < prev._current.DataClose) SellLong( this.StopLong,  EReason.OvernightStop);
            if (ShortQty > 0 && current.DataOpen > StopShort && StopShort > prev._current.DataClose) SellShort(this.StopShort, EReason.OvernightStop);

            //3. Open price arrives...
            //3a. Update values for anything held
            UpdateValues(current.DataOpen);

            //3b. Pull the plug when lost more than starting capital (optional)
            if (START + Profit < 0 && STOP_WHEN_SEED_CAPITAL_GONE)
            {
                SellLong( current.DataOpen, EReason.SeedCapitalIsGone);
                SellShort(current.DataOpen, EReason.SeedCapitalIsGone);
                return;
            }

            //3d. Act on overnight buy decisions
            switch (prev.Outlook)
            {
                case EOutlook.Long:
                    //Clear out any short positions not already cleared out by stop price
                    if (this.ShortQty > 0)
                        SellShort(current.DataOpen, EReason.YesterdaySaysLong);

                    //Transfer money to long position, unless open price has moved past the stop price
                    if (this.LongQty == 0 || KEEP_INVESTING_WHILE_BUY)
                        if (current.DataOpen > prev.StopLong || !CANCEL_OPEN_TRADES_IF_STOP)
                            BuyLong(current.DataOpen, investable, EReason.YesterdaySaysLong);
                        else
                            CancelLong(current.DataOpen);
                    break;

                case EOutlook.Short:
                    //Clear out any long positions
                    if (this.LongQty > 0)
                        SellLong(current.DataOpen, EReason.YesterdaySaysShort);

                    //Transfer money to short position, set stop price
                    if (this.ShortQty == 0 || KEEP_INVESTING_WHILE_BUY)
                        if (current.DataOpen < prev.StopShort || !CANCEL_OPEN_TRADES_IF_STOP)
                            BuyShort(current.DataOpen, investable, EReason.YesterdaySaysShort);
                        else
                            CancelShort(current.DataOpen);
                    break;

                case EOutlook.Stop:
                    //If not already cashed out automatically, do so now
                    if (this.LongQty > 0)
                        SellLong(current.DataOpen, EReason.YesterdaySaysStop);
                    if (this.ShortQty > 0)
                        SellShort(current.DataOpen, EReason.YesterdaySaysStop);
                    break;

                case EOutlook.Neutral:
                    break;
            }

            //4. Daytime trading - High/Low that trigger stops
            if (current.DataLow  < prev.StopLong  && current.DataHigh > prev.StopLong)  SellLong( prev.StopLong,  EReason.DaytimeStop);
            if (current.DataHigh > prev.StopShort && current.DataLow  < prev.StopLong)  SellShort(prev.StopShort, EReason.DaytimeStop);

            //5. End of day - Closing data arrives
            //5a. Update current values to todays closing price
            UpdateValues(current.DataClose);

            //5b. Revise outlook for tomorrow
            if (current.Long)
                Outlook = EOutlook.Long;
            else if (current.Short)
                Outlook = EOutlook.Short;
            else if (current.Stop)
                Outlook = EOutlook.Stop;
            else
                Outlook = EOutlook.Neutral;
        }
        #endregion

        #region Buying
        private void BuyLong(double price, double amount, EReason reason)
        {
            Note(ETrade.Buy, EDirection.Long, price, amount, reason);
            UpdateValues(price);
            this.Held -= amount;
            this.LongCv += amount;
            this.LongQty += amount / price;
        }
        private void BuyShort(double price, double amount, EReason reason)
        {
            Note(ETrade.Buy, EDirection.Short, price, amount, reason);
            UpdateValues(price);
            this.Held -= amount;
            this.ShortCv += amount;
            this.ShortQty += amount * price;
        }
        #endregion

        #region Selling
        private void SellShort(double price, EReason reason)
        {
            UpdateValues(price);
            if (this.ShortQty > 0)
            {
                Note(ETrade.Sell, EDirection.Short, price, this.ShortCv, reason);
                this.Held += this.ShortCv;
                this.ShortCv = 0;
                this.ShortQty = 0;
            }
        }
        private void SellLong(double price, EReason reason)
        {
            UpdateValues(price);

            if (LongQty > 0)
            {
                Note(ETrade.Sell, EDirection.Long, price, this.LongCv, reason);
                this.Held += this.LongCv;
                this.LongCv = 0;
                this.LongQty = 0;
            }
        }
        #endregion

        #region Helpers
        private void UpdateValues(double price)
        {
            this.LongCv = this.LongQty * price;
            this.ShortCv = this.ShortQty / price;
        }
        private void CancelLong(double price) { Cancel(price, EDirection.Long); }
        private void CancelShort(double price) { Cancel(price, EDirection.Short); }
        private void Cancel(double price, EDirection direction) { Note(ETrade.Cancel, direction, price, double.NaN, direction == EDirection.Long ? EReason.OpenBelowLongStop : EReason.OpenAboveShortStop); }
        private void Note(ETrade trade, EDirection direction, double price, double value, EReason reason)
        {
            this.Notes.Add(new CNote(trade, direction, price, value, reason));
        }
        #endregion
    }

    #region Note-Taking Structures
    public class CNoteList : List<CNote>
    {
        public CNoteList() : base() { }

        public override string ToString()
        {
            return CUtilities.ListToHtml(this);
        }
        public bool LongWasStopped
        {
            get
            {
                foreach (CNote i in this)
                    if (i.LongWasStopped)
                        return true;
                return false;
            }
        }
        public bool ShortWasStopped
        {
            get
            {
                foreach (CNote i in this)
                    if (i.ShortWasStopped)
                        return true;
                return false;
            }
        }
    }
    public class CNote
    {
        //Data
        public ETrade Trade;
        public EDirection Direction;
        public EReason Reason;
        public double Price;
        public double Value;

        //Constructors
        public CNote(ETrade trade, EDirection direction, double price, double value, EReason reason)
        {
            this.Trade = trade;
            this.Direction = direction;
            this.Price = price;
            this.Value = value;
            this.Reason = reason;
        }
        public override string ToString()
        {
            return string.Concat(this.Trade, "\t", double.IsNaN(this.Value) ? string.Empty : this.Value.ToString("C4"), "\t", this.Direction, "\t@\t", this.Price.ToString("F4"), "\tbecause\t", this.Reason, "\n");
        }
        public bool LongWasStopped  { get { return Direction == EDirection.Long  && WasStopped; } }
        public bool ShortWasStopped { get { return Direction == EDirection.Short && WasStopped; } } 
        public bool WasStopped
        {
            get
            {
                switch (Reason)
                {
                    case EReason.DaytimeStop:
                    case EReason.OvernightStop:
                    case EReason.YesterdaySaysStop:
                    case EReason.OpenBelowLongStop:
                    case EReason.OpenAboveShortStop:
                        return true;
                }
               return false;
            }
        }
    }
    #endregion
}

